<?php
    /**
     * Palautekoodit
     * ------------
     * 0    = OK
     * 10   = Rajapintavirhe
     * 20   = Tietokantavirhe
     * 30   = Validointivirhe
     * 999  = Tuntematon virhe
     */
    class RequestHandler {
        private static $_keyIteration = null;
        
        /**
         * Hae reasonKey:n kuvaus viittavan luokan sisältä jos kuvaus on määritetty
         * luokkaan.
         *
         * Palauttaa null jos kuvausta ei ole määritetty.
         *
         * @param   string  $source Lähdeluokka
         * @param   string  $key    reasonKey
         * @return  mixed
         */
        private static function _getKeyDesc($source,$key) {
            $result = null;
            
            if (!$source || !$key) {
                return $result;
            }
            
            if (!class_exists($source)) {
                return $result;
            }
            
            if (!array_key_exists("keyDesc",get_class_vars($source))) {
                return $result;
            }
            
            if (!is_array($source::$keyDesc) || !isset($source::$keyDesc[$key])) {
                return $result;
            }
            
            return $source::$keyDesc[$key];
        }
        
        /**
         * Hae virheen lähteet kuvaus.
         *
         * Palauttaa null jos kuvausta ei ole määritetty.
         *
         * @param   string  $source Lähdeluokka
         * @return  mixed
         */
        private static function _getSourceDesc($source) {
            $result = null;
            
            if (!$source) {
                return $result;
            }
            
            if (!class_exists($source)) {
                return $result;
            }
            
            if (!array_key_exists("classDesc",get_class_vars($source))) {
                return $result;
            }
            
            if (!is_string($source::$classDesc)) {
                return $result;
            }
            
            return $source::$classDesc;
        }
        
        /**
         * Lisää iteraatiokierros.
         *
         * Palauttaa kierroksen numeron tai boolean false jos lähdettä ei ole
         * määritetty.
         *
         * @param   string  $source Iteraation lähde
         * @return  mixed
         */
        public static function addIteration($source) {
            $result = false;
            
            if ($source) {
                if (!isset(self::$_keyIteration)) {
                    self::$_keyIteration = array();
                }
                
                if (isset(self::$_keyIteration[$source])) {
                    $result = ++self::$_keyIteration[$source];                    
                } else {
                    $result = self::$_keyIteration[$source] = 1;
                }
            }
            
            return $result;
        }
        
        /**
         * Tyhjennä iteraatio.
         *
         * @param   string  $source Iteraation lähde
         * @return  boolean
         */
        public static function clearIteration($source) {
            $result = false;
            
            if ($source && isset(self::$_keyIteration)) {
                if (isset(self::$_keyIteration[$source])) {
                    unset(self::$_keyIteration[$source]);
                    $result = true;
                    
                    if (empty(self::$_keyIteration)) {
                        self::$_keyIteration = null;
                    }
                }
            }                        
            
            return $result;
        }
        
        /**
         * Hae iteraatiot.
         *
         * @return  mixed
         */
        public static function getIteration() {
            return self::$_keyIteration;
        }                
        
        /**
         * Funktio käsittelee rajapintaan tulleen kutsun.
         *
         * Funktio palauttaa aina taulukon jossa on sarakkeet
         * code, reasonCode, reasonKey, text ja data.
         *
         * code         = palautekoodi (int)
         * reasonCode   = syykoodi, joka tarkentaa virheen (null tai int)
         * reasonSource = syyn lähde (esim. WorkoutModel)
         * reasonKey    = mahdollinen sarake datassa johon syykoodi liittyy (null tai string)
         * keyDesc      = reasonKey:n lyhyt selkokielinen kuvaus
         * iteration    = liittyykö virhe tiettyyn iteraatiokierrokseen
         * sourceDesc   = Syyn lähteen ja iteraatioiden mahdolliset selväkieliset kuvaukset
         * text         = palauteteksti (string tai array)
         * data         = paluudata (mixed)
         *
         * @param   string  $class      Kutsuttava luokka
         * @param   string  $function   Kutsuttava funktio
         * @param   array   $data       Data kutsuun (vapaaehtoinen)
         * @return  array
         */
        public static function process($class,$function,$data = array()) {
            $result = array(
                "code"          => 0, # 0 = OK
                "reasonCode"    => null,
                "reasonSource"  => null,
                "reasonKey"     => null,
                "keyDesc"       => null,
                "iteration"     => null,
                "sourceDesc"    => null,
                "text"          => "OK",
                "data"          => null
            );
            
            try {                
                # Luokan nimi puuttuu
                if (!is_string($class) || !$class) {
                    throw new APIException("Class name is missing",10,__CLASS__);    
                }
                
                # Funktion nimi puuttuu
                if (!is_string($function) || !$function) {
                    throw new APIException("Function name is missing",20,__CLASS__);
                }
                
                # Data ei ole taulukko
                if (!is_array($data)) {
                    throw new APIException("Data is not an valid array",30,__CLASS__);
                }
                
                # Lisää Control pääte luokan nimeen
                if (!mb_ereg_match("Control$",$class)) {
                    $class .= "Control";
                }
                
                # Tarkasta käyttöoikeus ja kirjautuminen
                LoginControl::checkAccess($class,$function);
                
                # Kutsu funktiota luokasta joka prosessoi kutsun
                $result["data"] = $class::$function($data);
            } catch (Exception $e) {                                
                $code           = 999; # Tuntematon virhe
                $reasonCode     = null;
                $reasonSource   = null;
                $reasonKey      = null;
                $keyDesc        = null;                
                $iteration      = null;
                $sourceDesc     = null;
                $text           = "Unknown error occured!";
                
                switch (get_class($e)) {
                    case "APIException": # Rajapintavirhe
                        $code           = 10;
                        $reasonCode     = (int)$e->getCode();
                        $reasonSource   = $e->class;
                        $iteration      = self::getIteration();
                        $text           = $e->getMessage();
                        break;
                    
                    case "DBException": # Tietokantavirhe
                        $code           = 20;
                        $reasonCode     = (int)$e->getCode();
                        $reasonSource   = $e->class;
                        $iteration      = self::getIteration();
                        $text           = $e->forUser;
                        break;
                    
                    case "ValidationException": # Validointivirhe
                        $code           = 30;
                        $reasonCode     = (int)$e->getCode();
                        $reasonSource   = $e->class;
                        $reasonKey      = $e->key;
                        $iteration      = self::getIteration();
                        $text           = $e->getMessage();
                        
                        # reasonKey:n kuvaus
                        $keyDesc = self::_getKeyDesc($reasonSource,$reasonKey);
                        break;
                }
                
                # Hae lähteen kuvaukset
                if ($reasonSource) {
                    $desc = self::_getSourceDesc($reasonSource);
                    if ($desc) {
                        if (!isset($sourceDesc)) {
                            $sourceDesc = array();
                        }
                        $sourceDesc[$reasonSource] = $desc;
                    }
                }
                
                foreach ((array)$iteration as $source => $index) {
                    $desc = self::_getSourceDesc($source);
                    if ($desc) {
                        if (!isset($sourceDesc)) {
                            $sourceDesc = array();
                        }
                        $sourceDesc[$source] = $desc;
                    }
                }
                
                $result["code"]         = $code;
                $result["reasonCode"]   = $reasonCode;
                $result["reasonSource"] = $reasonSource;
                $result["reasonKey"]    = $reasonKey;
                $result["keyDesc"]      = $keyDesc;
                $result["iteration"]    = $iteration;
                $result["sourceDesc"]   = $sourceDesc;
                $result["text"]         = $text;
            }
            
            return $result;
        }
    }